package io.hepu.robotize.auth;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import io.hepu.robotize.IConstant;
import io.hepu.robotize.IContext;
import io.hepu.robotize.enums.AuthPlatform;
import io.hepu.robotize.enums.AuthType;
import io.hepu.robotize.util.JBase64;
import io.hepu.robotize.util.JDate;
import io.hepu.robotize.util.Joins;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;

import java.util.Date;
import java.util.Map;
import java.util.Objects;

/**
 * 将获取加密字符串：由getUserName -> getUserId
 * 同时加入：authorization platform
 */
public class Jwts {
    private static final Logger LOG = LoggerFactory.getLogger(Jwts.class);

    // TOKEN EXPIRATION
    private static final int EXPIRATION = IConstant.JWT_ACCESS_TOKEN_EXPIRATION;

    // Bearer token prefix
    private static final String AUTH_SCHEME_BEARER = "Bearer ";

    public static String trimScheme(@NonNull String authorization) {
        Objects.requireNonNull(authorization, "认证信息不能为空");
        return authorization.substring(AUTH_SCHEME_BEARER.length());
    }

    // Verify the token whether correct or not.
    // Exceptions must be thrown otherwise the Token cannot be refreshed
    public static boolean verify(@NonNull String token) {
        String secret = getSecret(getSubject(token));
        Algorithm algorithm = Algorithm.HMAC256(secret);
        JWTVerifier verifier = JWT.require(algorithm).build();
        DecodedJWT jwt = verifier.verify(token);
        return jwt.getToken() != null;
    }

    public static boolean valid(@NonNull String token) {
        try {
            return verify(token);
        } catch (Exception e) {
            // ignore exception
            return false;
        }
    }

    /**
     * Generate signature
     * @param subject subject claim
     * @param payload custom claims
     * @param timestamp issued time
     * @return token
     */
    public static String sign(@NonNull String subject, @Nullable Map<String, Object> payload, @NonNull long timestamp) {
        String secret = getSecret(subject);
        return JWT.create()
                .withSubject(subject)
                .withPayload(payload)
                .withIssuedAt(new Date(timestamp))
                .withExpiresAt(createExpiresAt(timestamp, EXPIRATION))
                .sign(getAlgorithm(secret));
    }

    // Generate signature
    public static String sign(@NonNull String token, @NonNull long timestamp) {
        String secret = getSecret(getSubject(token));
        return JWT.create()
                .withSubject(getSubject(token))
                .withPayload(getClaims(token))
                .withIssuedAt(new Date(timestamp))
                .withExpiresAt(createExpiresAt(timestamp, EXPIRATION))
                .sign(getAlgorithm(secret));
    }

    // ----------------------------
    //     Access methods         |
    // ----------------------------

    // Generate JWT secret with realm which is user id or username etc., authorization platform and salt string.
    // Here: user id : platform : salt
    public static String getSecret(String realm) {
        String source = Joins.concat(realm, IContext.getAuthzPlatform(), IConstant.JWT_ENCRYPT_KEY);
        LOG.trace("[-------> ::: JWT ::: verify or signing secret source: {}]", source);
        return JBase64.encode(source);
    }

    // Get Algorithm
    public static Algorithm getAlgorithm(String secret) {
        return Algorithm.HMAC256(secret);
    }

    // Get DecodedJWT
    public static DecodedJWT requireJwt(String token) {
        DecodedJWT jwt = JWT.decode(token);
        return Objects.requireNonNull(jwt, "DecodedJWT cannot be NULL");
    }

    // Get subject
    public static String getSubject(String token) {
        return requireJwt(token).getSubject();
    }

    // Get claim
    public static Claim getClaim(String token, String name) {
        return requireJwt(token).getClaim(name);
    }

    // Get claims
    public static Map<String, Claim> getClaims(String token) {
        return requireJwt(token).getClaims();
    }

    // Get IssuedAt
    public static Date getIssuedAt(String token) {
        return requireJwt(token).getIssuedAt();
    }

    // Get ExpiresAt
    public static Date getExpiresAt(String token) {
        return requireJwt(token).getExpiresAt();
    }

    // Get SigningAt
    public static Long getSigningAt(String token) {
        return getClaim(token, IConstant.CLAIM_SIGNING_AT).asLong();
    }

    public static AuthType getAuthType(String token) {
        String type = getClaim(token, IConstant.CLAIM_AUTH_TYPE).asString();
        return AuthType.valueOf(type);
    }

    public static AuthPlatform getAuthPlatform(String token) {
        String type = getClaim(token, IConstant.CLAIM_AUTH_PLATFORM).asString();
        return AuthPlatform.valueOf(type);
    }

    public static Date createExpiresAt(long signingAt, int expire) { // UNIT: MS
        return JDate.parse(signingAt + expire * 1000L);
    }

    public static long ttl(String token) {
        Date expiresAt = getExpiresAt(token);
        return expiresAt.getTime() - System.currentTimeMillis();
    }
}
